﻿using System;
using System.Buffers;
using System.Runtime.InteropServices;
using Deer.Hash;
using Deer.Time;
using Letter.IO.Binary;

namespace Telex;

sealed partial class RpcChannel
{
    private bool TryWriteSendPak(Guid uid, uint protocolId, byte[] request)
    {
        return this.TryWriteMessagePak(uid, Command.SEND, protocolId, request, TelexOpcode.SUCCEED);
    }

    private bool TryWriteRequestPak(Guid uid, uint protocolId, byte[] request)
    {
        return this.TryWriteMessagePak(uid, Command.REQUEST, protocolId, request, TelexOpcode.SUCCEED);
    }

    private bool TryWriteResponsePak(Guid uid, uint protocolId, byte[] response, int opcode)
    {
        return this.TryWriteMessagePak(uid, Command.RESPONSE, protocolId, response, opcode);
    }

    private bool TryWriteMessagePak(Guid uid, byte command, uint protocolId, byte[] data, int opcode)
    {
        this.CreateMessagePack(uid, protocolId, data, opcode, out var mPack);
        this.CreateSocketPakFromMessage(command, ref mPack, out var pak);
        
        var unit = new VerifyUnit()
        {
            pak = pak,
            timestamp = TimeUtil.GetTimestamp(DateTime.UtcNow.AddSeconds(TIMEOUT))
        };
        
        if (!this.waitVerifyUnits.TryAdd(pak.hash, unit))
        {
            return false;
        }
        
        return this.TryWriteChannel(ref pak);
    }
    
    private bool TryWriteVerifyPack(uint hash, byte status)
    {
        this.CreateVerifyPack(hash, status, out var vPack);
        this.CreateSocketPakFromVerify(ref vPack, out var pak);
        return TryWriteChannel(ref pak);
    }

    private bool TryWriteChannel(ref SocketPak pak)
    {
        int pipelineCount = this.pipelines.Count;
        if (pipelineCount == 0)
        {
            this.logger.Error("The socket pipeline does not exist");
            return false;
        }

        if (pipelineCount == 1)
        {
            return this.pipelines[0].TryWriteSndChannel(ref pak);
        }

        this.pipelines.Sort((x, y) => -x.CompareTo(y));
        return this.pipelines[0].TryWriteSndChannel(ref pak);
    }
    
    private void CreateMessagePack(Guid uid, uint protocolId, byte[] data, int opcode, out MessagePack pack)
    {
        pack = new MessagePack()
        {
            uid = uid,
            protocolId = protocolId,
            opcode = opcode,
            data = data
        };
    }
    
    private void CreateVerifyPack(uint hash, byte status, out VerifyPack pack)
    {
        pack = new VerifyPack()
        {
            hash = hash,
            status = status
        };
    }
    
    public void CreateSocketPakFromVerify(ref VerifyPack pack, out SocketPak pak)
    {
        int size = FIXED_VERIFY_PACK_SIZE;
        var sourceArr = ArrayPool<byte>.Shared.Rent(size);
        var segment = sourceArr.AsSegment(0, size);
         
        int index = 0;
        var buf = segment.AsSpan();
        buf.Slice(index, sizeof(uint)).FWriteUInt32BE(pack.hash);
        index += sizeof(uint);

        buf.Slice(index, sizeof(byte)).FWriteUInt8(pack.status);
        index += sizeof(byte);

        pak = new SocketPak()
        {
            command = Command.VERIFY,
            hash = 0,
            buf = segment
        };
    }
     
    private void CreateSocketPakFromMessage(byte command, ref MessagePack pack, out SocketPak pak)
    {
        int size = FIXED_MESSAGE_PACK_SIZE + pack.data.Length;
        var sourceArr = ArrayPool<byte>.Shared.Rent(size);
        var segment = sourceArr.AsSegment(0, size);

        int index = 0;
        var buf = segment.AsSpan();

        MemoryMarshal.Write(buf.Slice(index, UID_SIZE), ref pack.uid);
        index += UID_SIZE;

        buf.Slice(index, sizeof(uint)).FWriteUInt32BE(pack.protocolId);
        index += sizeof(uint);

        buf.Slice(index, sizeof(int)).FWriteInt32BE(pack.opcode);
        index += sizeof(int);

        buf.Slice(index, pack.data.Length).FWriteSpan(pack.data);
        index += pack.data.Length;

        pak = new SocketPak()
        {
            command = (byte)command,
            hash = HashGenerator.Create(buf),
            buf = segment
        };
    }
}